#pragma once

#include <unordered_set>
#include "object/objectguid.h"

namespace battle::object
{
    struct WorldLocation;
    class Object;
    class Corpse;
    class Creature;
    class DynamicObject;
    class GameObject;
    class Pet;
    class Player;
    class Camera;
    class Unit;
}
class Map;

typedef std::set<ObjectGuid> GuidSet;
typedef std::list<ObjectGuid> GuidList;
typedef std::vector<ObjectGuid> GuidVector;

// enum on which Events an action for linked NPCs can trigger
enum CreatureLinkingEvent
{
    LINKING_EVENT_AGGRO = 0,
    LINKING_EVENT_EVADE = 1,
    LINKING_EVENT_DIE = 2,
    LINKING_EVENT_RESPAWN = 3,
    LINKING_EVENT_DESPAWN = 4,
};

// enum describing possible flags action flags for NPCs linked to other NPCs
// These flags are actually put into the database
// FLAG_TO_ means, that in this case the linked NPC will also trigger an action for the NPC it is linked to
enum CreatureLinkingFlags
{
    // Dynamic behaviour, in combat
    FLAG_AGGRO_ON_AGGRO = 0x0001,
    FLAG_TO_AGGRO_ON_AGGRO = 0x0002,
    FLAG_RESPAWN_ON_EVADE = 0x0004,
    FLAG_TO_RESPAWN_ON_EVADE = 0x0008,
    FLAG_DESPAWN_ON_EVADE = 0x1000,
    FLAG_DESPAWN_ON_DEATH = 0x0010,
    FLAG_SELFKILL_ON_DEATH = 0x0020,
    FLAG_RESPAWN_ON_DEATH = 0x0040,
    FLAG_RESPAWN_ON_RESPAWN = 0x0080,
    FLAG_DESPAWN_ON_RESPAWN = 0x0100,

    // Dynamic behaviour, out of combat
    FLAG_FOLLOW = 0x0200,
    FLAG_DESPAWN_ON_DESPAWN = 0x2000,

    // Passive behaviour
    FLAG_CANT_SPAWN_IF_BOSS_DEAD = 0x0400,
    FLAG_CANT_SPAWN_IF_BOSS_ALIVE = 0x0800,

    LINKING_FLAG_INVALID = 0x4000,               // TODO adjust when other flags are implemented
};

// Structure holding the information for an entry
struct CreatureLinkingInfo
{
    unsigned int mapId;
    unsigned int masterId;
    unsigned int masterDBGuid;
    unsigned short linkingFlag : 16;
    unsigned short searchRange : 16;
};

/**
 * A class to represent the static information of linking NPCs together
 */

class CreatureLinkingMgr
{
public:                                                 // Constructors
    CreatureLinkingMgr() {}

public:                                                 // Initialisation
    void LoadFromDB();

public:                                                 // Accessors
    // This functions checks if the NPC triggers actions for other NPCs
    bool IsLinkedEventTrigger(battle::object::Creature* pCreature) const;

    // This function checks if the NPC is a master NPC.
    bool IsLinkedMaster(battle::object::Creature* pCreature) const;

    // This function checks if the spawning of this NPC is dependend on other NPCs
    bool IsSpawnedByLinkedMob(battle::object::Creature* pCreature) const;
    bool IsSpawnedByLinkedMob(CreatureLinkingInfo const* pInfo) const;

    // This gives the information of a linked NPC (describes action when its ActionTrigger triggers)
    // Depends of the map
    CreatureLinkingInfo const* GetLinkedTriggerInformation(battle::object::Creature* pCreature) const;
    CreatureLinkingInfo const* GetLinkedTriggerInformation(unsigned int entry, unsigned int lowGuid, unsigned int mapId) const;

private:
    typedef std::multimap < unsigned int /*slaveEntry*/, CreatureLinkingInfo > CreatureLinkingMap;
    typedef std::pair<CreatureLinkingMap::const_iterator, CreatureLinkingMap::const_iterator> CreatureLinkingMapBounds;

    // Storage of Data: npc_entry_slave, (map, npc_entry_master, flag, master_db_guid[If Unique], search_range)
    CreatureLinkingMap m_creatureLinkingMap;
    // Storage of Data: npc_guid_slave, (map, npc_guid_master, flag, master_db_guid, search_range)
    CreatureLinkingMap m_creatureLinkingGuidMap;

    // Lookup Storage for fast access:
    std::unordered_set<unsigned int> m_eventTriggers;              // master by entry
    std::unordered_set<unsigned int> m_eventGuidTriggers;          // master by guid

    // Check-routine
    static bool IsLinkingEntryValid(unsigned int slaveEntry, CreatureLinkingInfo* pInfo, bool byEntry);
};

/**
 * A class to represent the dynamic information of linking NPCs together
 *
 * Every map has an instance of this class as member, in which the dynamic information (GUIDs) are stored
 */

class CreatureLinkingHolder
{
public:                                                 // Constructors
    CreatureLinkingHolder() {}

public:                                                 // Accessors
    // Function to add slave-NPCs to the holder
    void AddSlaveToHolder(battle::object::Creature* pCreature);

    // Function to add master-NPCs to the holder
    void AddMasterToHolder(battle::object::Creature* pCreature);

    // Function to process actions for linked NPCs
    void DoCreatureLinkingEvent(CreatureLinkingEvent eventType, battle::object::Creature* pSource, battle::object::Unit* pEnemy = NULL);

    // Function to check if a passive spawning condition is met
    bool CanSpawn(battle::object::Creature* pCreature) const;

    // This function lets a slave refollow his master
    bool TryFollowMaster(battle::object::Creature* pCreature);

private:
    // Structure associated to a master (entry case)
    struct InfoAndGuids
    {
        unsigned short linkingFlag : 16;
        unsigned short searchRange : 16;
        GuidList linkedGuids;
    };
    // Structure associated to a master (guid case)
    struct InfoAndGuid
    {
        unsigned short linkingFlag;
        ObjectGuid linkedGuid;
    };

    typedef std::multimap < unsigned int /*masterEntryOrGuid*/, InfoAndGuids > HolderMap;
    typedef std::pair<HolderMap::iterator, HolderMap::iterator> HolderMapBounds;
    typedef std::multimap < unsigned int /*Entry*/, ObjectGuid > BossGuidMap;
    typedef std::pair<BossGuidMap::const_iterator, BossGuidMap::const_iterator> BossGuidMapBounds;

    // Helper function, to process a slave list
    void ProcessSlaveGuidList(CreatureLinkingEvent eventType, battle::object::Creature* pSource, unsigned int flag, unsigned short searchRange, GuidList& slaveGuidList, battle::object::Unit* pEnemy);
    // Helper function, to process a single slave
    void ProcessSlave(CreatureLinkingEvent eventType, battle::object::Creature* pSource, unsigned int flag, battle::object::Creature* pSlave, battle::object::Unit* pEnemy);
    // Helper function to set following
    void SetFollowing(battle::object::Creature* pWho, battle::object::Creature* pWhom);
    // Helper function to return if a slave is in range of a boss
    bool IsSlaveInRangeOfBoss(battle::object::Creature const* pSlave, battle::object::Creature const* pBoss, unsigned short searchRange) const;
    bool IsSlaveInRangeOfBoss(battle::object::Creature const* pBoss, float slaveX, float slaveY, unsigned short searchRange) const;
    // Another helper function
    bool IsRespawnReady(unsigned int dbLowGuid, Map* _map) const;
    // Helper function for recursive spawning-checks of an additional linked
    bool CanSpawn(unsigned int lowGuid, Map* _map, CreatureLinkingInfo const* pInfo, float sx, float sy) const;

    // Storage of Data (boss, flag, searchRange, GuidList) for action triggering
    HolderMap m_holderMap;
    // Storage of Data (boss, flag, slave-guid)
    HolderMap m_holderGuidMap;
    // boss_entry, guid for reverse action triggering and check alive
    BossGuidMap m_masterGuid;
};
